/* 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.harmony.javax.naming;

import java.io.Serializable;
import java.util.Enumeration;
import java.util.Vector;

import org.apache.harmony.jndi.internal.nls.Messages;

/**
 * A <code>Reference</code> contains the class of the object which is referenced
 * together with a list of all the addresses at which this object may be found.
 * Additionally the <code>Reference</code> has the location of a factory which
 * can create this object together with the classname used to create the object.
 * <p>
 * The <code>Reference</code> class relates to objects which are not bound in a
 * naming service but which need to be accessed via
 * org.apache.harmony.javax.naming. <code>Reference</code>, <code>RefAddr</code>
 * and their subclasses provide a way to access objects other than those in
 * naming and directory systems.
 * </p>
 */
public class Reference implements Cloneable, Serializable {

	private static final long serialVersionUID = -1673475790065791735L;

	/**
	 * The class of the object which is referenced.
	 * 
	 * @serial
	 */
	protected String className;

	/**
	 * A list of the addresses provided for this object. The default is null.
	 * 
	 * @serial
	 */
	protected Vector<RefAddr> addrs;

	/**
	 * The class in a factory which is used to create an object of the type
	 * which is referenced. The default is null.
	 * 
	 * @serial
	 */
	protected String classFactory;

	/**
	 * The location of class <code>classFactory</code>. To find class files the
	 * URL of the classfile is given. If there is more than one URL for the
	 * class then a list of URLs appears in the string using a space as a
	 * separator. The default is null.
	 * 
	 * @serial
	 */
	protected String classFactoryLocation;

	/**
	 * Constructs a <code>Reference</code> instance with an empty list of
	 * addresses using the supplied class name.
	 * 
	 * @param className
	 *            the class of the object which is referenced. It cannot be
	 *            null.
	 */
	public Reference(String className) {
		this(className, null, null);
	}

	/**
	 * Constructs a <code>Reference</code> instance with one entry of address.
	 * 
	 * @param className
	 *            the class of the object which is referenced. It cannot be
	 *            null.
	 * @param addr
	 *            the object's address. It cannot be null.
	 */
	public Reference(String className, RefAddr addr) {
		this(className, addr, null, null);
	}

	/**
	 * Constructs a <code>Reference</code> instance with one entry of address
	 * using the supplied class name, factory class and factory location.
	 * 
	 * @param className
	 *            the class of the object which is referenced. It cannot be
	 *            null.
	 * @param addr
	 *            the object's address. It cannot be null.
	 * @param classFactory
	 *            the class in a factory which is used to create an object of
	 *            the type which is referenced. It may be null.
	 * @param classFactoryLocation
	 *            the location of the class file. It may be null.
	 */
	public Reference(String className, RefAddr addr, String classFactory,
			String classFactoryLocation) {
		this(className, classFactory, classFactoryLocation);
		addrs.add(addr);
	}

	/**
	 * Constructs a <code>Reference</code> instance with an empty list of
	 * addresses using the supplied class name, factory class and factory
	 * location.
	 * 
	 * @param className
	 *            the class of the object which is referenced. It cannot be
	 *            null.
	 * @param classFactory
	 *            the class in a factory which is used to create an object of
	 *            the type which is referenced. It may be null.
	 * @param classFactoryLocation
	 *            the location of the class file. It may be null.
	 */
	public Reference(String className, String classFactory,
			String classFactoryLocation) {
		super();
		this.className = className;
		this.classFactory = classFactory;
		this.classFactoryLocation = classFactoryLocation;
		addrs = new Vector<RefAddr>();
	}

	/**
	 * Inserts an address within the list at the specified index.
	 * 
	 * @param addr
	 *            the address to insert into the list. It cannot be null.
	 * @param index
	 *            the index where to insert the address. It must be greater than
	 *            or equal to 0 and less than or equal to the number of entries
	 *            in the list(size()).
	 * @throws ArrayIndexOutOfBoundsException
	 *             If the index is invalid.
	 */
	public void add(int index, RefAddr addr) {
		addrs.add(index, addr);
	}

	/**
	 * Appends an address to the list.
	 * 
	 * @param addr
	 *            the address to append. It cannot be null.
	 */
	public void add(RefAddr addr) {
		addrs.add(addr);
	}

	/**
	 * Deletes all the addresses from the address list.
	 */
	public void clear() {
		addrs.clear();
	}

	/**
	 * Returns a deep clone of this <code>Reference</code> instance.
	 * 
	 * @return a deep clone of this object
	 */
	@SuppressWarnings("unchecked")
	@Override
	public Object clone() {
		try {
			final Reference reference = (Reference) super.clone();
			reference.addrs = (Vector<RefAddr>) addrs.clone();
			return reference;
		} catch (final CloneNotSupportedException e) {
			// jndi.03=Failed to clone object of Reference class.
			throw new AssertionError(Messages.getString("jndi.03")); //$NON-NLS-1$
		}
	}

	/**
	 * Returns true if this <code>Reference</code> instance is equal to the
	 * supplied object <code>o</code>. They are considered equal if their class
	 * name and list of addresses are equivalent (including the order of the
	 * addresses in the list). The factory class and location are ignored for
	 * the purposes of this comparison.
	 * 
	 * @param o
	 *            the object to compare with
	 * @return true if this object is equal to <code>o</code>, otherwise false
	 */
	@Override
	public boolean equals(Object o) {
		if (o == this) {
			return true;
		}
		if (!(o instanceof Reference)) {
			return false;
		}
		final Reference ref = (Reference) o;
		return ref.className.equals(className)
				&& ref.addrs.size() == addrs.size() && ref.addrs.equals(addrs);
	}

	/**
	 * Gets the address held at the specified index in the address list.
	 * 
	 * @param index
	 *            the index of the required address. It must be greater than or
	 *            equal to 0 and less than the number of entries in the list.
	 * @return the address held at the specified index
	 * @throws ArrayIndexOutOfBoundsException
	 *             If the index is invalid.
	 */
	public RefAddr get(int index) {
		return addrs.get(index);
	}

	/**
	 * Gets an address where the address type matches the specified string.
	 * There may be more than one entry in the list with the same address type
	 * but this method returns the first one found in the list.
	 * 
	 * @param type
	 *            the address type to look for
	 * @return the first address whose type matches the string
	 */
	public RefAddr get(String type) {
		final Enumeration<RefAddr> elements = addrs.elements();
		RefAddr refAddr = null;

		while (elements.hasMoreElements()) {
			refAddr = elements.nextElement();
			if (type.equals(refAddr.getType())) {
				return refAddr;
			}
		}
		return null;
	}

	/**
	 * Gets all the addresses.
	 * 
	 * @return an enumeration of all the addresses
	 */
	public Enumeration<RefAddr> getAll() {
		return addrs.elements();
	}

	/**
	 * Gets the class of the object which is referenced. The result cannot be
	 * null.
	 * 
	 * @return the class of the object which is referenced
	 */
	public String getClassName() {
		return className;
	}

	/**
	 * Gets the location of the factory class. The result may be null.
	 * 
	 * @return the location of the factory class
	 */
	public String getFactoryClassLocation() {
		return classFactoryLocation;
	}

	/**
	 * Gets the class in a factory which is used to create an object of the type
	 * which is referenced. The result may be null.
	 * 
	 * @return the class in a factory which is used to create the referenced
	 *         object
	 */
	public String getFactoryClassName() {
		return classFactory;
	}

	/**
	 * Returns the hashcode for this <code>Reference</code> instance. The result
	 * is calculated by summing the hashcode of the class name and the hash
	 * codes of each of the addresses in the address list.
	 * 
	 * @return the hashcode of this <code>Reference</code> instance
	 */
	@Override
	public int hashCode() {
		int hashCode = className.hashCode();
		final Enumeration<RefAddr> e = addrs.elements();
		while (e.hasMoreElements()) {
			hashCode += e.nextElement().hashCode();
		}
		return hashCode;
	}

	/**
	 * Removes an address from the address list.
	 * 
	 * @param index
	 *            the index of the address to remove. It must be greater than or
	 *            equal to 0 and less than size().
	 * @return the removed address
	 * @throws ArrayIndexOutOfBoundsException
	 *             If the index is invalid.
	 */
	public Object remove(int index) {
		return addrs.remove(index);
	}

	/**
	 * Gets the number of addresses in the address list.
	 * 
	 * @return the size of the list
	 */
	public int size() {
		return addrs.size();
	}

	/**
	 * Returns the string representation of the class name together with the
	 * list of addresses.
	 * 
	 * @return the string representation of this object
	 */
	@Override
	public String toString() {
		final StringBuilder sb = new StringBuilder("Reference Class Name: "); //$NON-NLS-1$
		sb.append(className).append('\n');
		if (addrs.size() > 0) {
			final Enumeration<RefAddr> elements = addrs.elements();
			while (elements.hasMoreElements()) {
				sb.append(elements.nextElement().toString());
			}
		}
		return sb.toString();
	}
}
